/*******************************************************************************
  Copyright(c) 2009 Geoffrey Hausheer. All rights reserved.
  Copyright(c) 2000 - 2003 Radu Corlan. All rights reserved.
  
  This program is free software; you can redistribute it and/or modify it 
  under the terms of the GNU General Public License as published by the Free 
  Software Foundation; either version 2 of the License, or (at your option) 
  any later version.
  
  This program is distributed in the hope that it will be useful, but WITHOUT 
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for 
  more details.
  
  You should have received a copy of the GNU General Public License along with
  this program; if not, write to the Free Software Foundation, Inc., 59 
  Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  
  The full GNU General Public License is included in this distribution in the
  file called LICENSE.
  
  Contact Information: gcx@phracturedblue.com
*******************************************************************************/
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <sys/io.h>
#include <sched.h>
#include <unistd.h>
#include <stdlib.h>
#include <sched.h>
#include <math.h>
#include <errno.h>
#include <stdlib.h>
#include <getopt.h>
#include <termios.h>
#include <string.h>
#include <time.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <ctype.h>
#include <usb.h>


int debug = 0;

#define dprintf if(debug) printf

#define STORE_WORD_BE(var, val) *(var) = ((val) >> 8) & 0xff; *((var) + 1) = (val) & 0xff

#define PORT    5555
#define MAXMSG  16386

int width, height, real_height;
double expose = 0.1;
struct usb_dev_handle *usbhandle;
int status = 1;

static fd_set active_fd_set, read_fd_set;
static int base_sock;
static unsigned char *image;
static unsigned short *mapimage;
static unsigned char *image_ptr;

void map_image(unsigned short *map, unsigned char *image, int width, int height)
{
	int row, col;
	int x_offset = (1280 - width) / 2;

	for(row = 0; row < height - 26; row++)
		for(col = x_offset + 20; col < width + x_offset + 20; col++)
			*map++ = image[row * 1558 + col];
}

usb_dev_handle *locate_device(unsigned int vid, unsigned int pid) 
{
	unsigned char located = 0;
	struct usb_bus *bus;
	struct usb_device *dev;
	usb_dev_handle *device_handle = 0;
 		
	usb_find_busses();
	usb_find_devices();
 
 	for (bus = usb_busses; bus; bus = bus->next)
	{
		for (dev = bus->devices; dev; dev = dev->next)	
		{
			if (dev->descriptor.idVendor == vid
				&& dev->descriptor.idProduct == pid)
			{
				located++;
				device_handle = usb_open(dev);
				dprintf("Device Found: %s \n", dev->filename);
				dprintf("Vendor ID 0x0%x\n",dev->descriptor.idVendor);
				dprintf("Product ID 0x0%x\n",dev->descriptor.idProduct);
			}
		}	
  }

  if (device_handle==0) return (0);
	int open_status = usb_set_configuration(device_handle,1);

	open_status = usb_claim_interface(device_handle,0);
	open_status = usb_set_altinterface(device_handle,0);
	return (device_handle);  	
}

int ctrl_msg(usb_dev_handle *handle, unsigned char request_type, unsigned char request, unsigned int value, unsigned int index, unsigned char *data, unsigned char len)
{
	int i, result;
	
	dprintf("Sending %s command 0x%02x, 0x%02x, 0x%04x, 0x%04x, %d bytes\n",
		(request_type & USB_ENDPOINT_IN) ? "recv" : "send",
		request_type, request, value, index, len);
	result =  usb_control_msg(handle, request_type,
				request, value, index, data, len, 5000);
	for(i = 0; i < len; i++) {
		dprintf(" %02x", data[i]);
	}
	dprintf("\n");
	return result;
}

void start_read(usb_dev_handle *handle, unsigned int exposure)
{
	int index, value;

	unsigned char buffer[10] = "DEADEADEAD"; // for debug purposes
	index = exposure >> 16;
	value = exposure & 0xffff;
	
	usleep(20000);
	ctrl_msg(handle, 0xc2, 0x12, value, index, buffer, 2);
}

void fetch_image(usb_dev_handle *handle, unsigned char *image, unsigned int image_size)
{
	int result, i;
	dprintf("Reading %08x bytes\n", image_size);
	result = usb_bulk_read(handle, 0x82, image, image_size, 20000);
	if (result > 0) {
		dprintf( "Bytes: %d\n", result);
	} else {
		printf("Failed to read image: %d\n", result);
		exit(1);
	}
}

int read_image(usb_dev_handle *handle, unsigned int exposure, unsigned char *image, unsigned int image_size)
{

	int result;
	
	usleep(20000);
	start_read(handle, exposure);
	usleep(20000);
	fetch_image(handle, image, image_size);

	return(0);
}


int set_size(usb_dev_handle *handle, int height, int gain, int first_time)
{
	unsigned char reg[19];
	int offset, value, index;

        height -=  height % 4;
	offset = (1048 - height) / 2;
	index = (1558 * (height + 26)) >> 16;
	value = (1558 * (height + 26)) & 0xffff;
	gain = gain * 0x6ff / 100;
	STORE_WORD_BE(reg + 0,  gain);
	STORE_WORD_BE(reg + 2,  gain);
	STORE_WORD_BE(reg + 4,  gain);
	STORE_WORD_BE(reg + 6,  gain);
	STORE_WORD_BE(reg + 8,  offset);
	STORE_WORD_BE(reg + 10, 0);
	STORE_WORD_BE(reg + 12, height - 1);
	STORE_WORD_BE(reg + 14, 0x0521);
	STORE_WORD_BE(reg + 16, height + 25);
	reg[18] = 0xcc;
	ctrl_msg(handle, 0x42, 0x13, value, index, reg, 19);
	usleep(20000);
	ctrl_msg(handle, 0x42, 0x14, 0x31a5, 0, reg, 0);
	usleep(10000);
	ctrl_msg(handle, 0x42, 0x16, first_time ? 1 : 0, 0, reg, 0);
	return(height + 26);
}

static int make_socket (uint16_t port)
{
	int sock;
	struct sockaddr_in name;
	int one = 1;
     
	/* Create the socket. */
	sock = socket (PF_INET, SOCK_STREAM, 0);
	if (sock < 0)
	{
		perror("socket error");
		return -1;
	}
	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));  
	/* Give the socket a name. */
	name.sin_family = AF_INET;
	name.sin_port = htons (port);
	name.sin_addr.s_addr = htonl (INADDR_ANY);
	if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0) {
		perror ("bind error");
		return  -1;
	}
     
	return sock;
}

int init_comms(void)
{
	/* Create the socket and set it up to accept connections. */
	base_sock = make_socket (PORT);
	if (listen (base_sock, 1) < 0)
	{
		perror ("listen");
		exit (EXIT_FAILURE);
	}
     	/* Initialize the set of active sockets. */
	FD_ZERO (&active_fd_set);
	FD_SET (base_sock, &active_fd_set);
	return 0;
}

double make_jdate(int y, int m, int d, double hours)
{
        int jdi, i, j, k;
        double jd;

        if (y < 100)
                y += 1900;

// do the julian date (per hsaa p107)
        i = y;
        j = m;
        k = d;
        jdi = k - 32075 + 1461 * (i + 4800 + (j - 14) / 12) / 4
                + 367 * (j - 2 - (j - 14) / 12 * 12) / 12
                - 3 * (( i + 4900 + (j - 14) / 12) / 100) / 4;

        jd = jdi - 0.5 + hours / 24.0;
        return jd;
}

double time_to_jdate(time_t t)
{
        struct tm *gmt;
        double jd;

        gmt = gmtime(&t);
// do the julian date (per hsaa p107)
        jd = make_jdate(1900 + gmt->tm_year, gmt->tm_mon + 1, gmt->tm_mday,
                        gmt->tm_hour + gmt->tm_min / (60.0)
                        + gmt->tm_sec / (3600.0));
        return jd;
}

static int read_from_client (int filedes)
{
	char buffer[MAXMSG+1], recv[MAXMSG+1] = {0};
	int nbytes;
	char *resp, *bufptr, *tok, *tokptr;
     
	nbytes = read (filedes, buffer, MAXMSG);
	if (nbytes < 0) {
		/* Read error. */
		perror ("read");
		exit (EXIT_FAILURE);
	} else if (nbytes == 0) {
		/* End-of-file. */
		return -1;
	}
	buffer[nbytes] = 0;
	bufptr = buffer;
	//printf("--------------\n%s\n--------------\n", buffer);
	while(tok = strtok_r(bufptr, "\n", &tokptr)) {
		bufptr = NULL;
		recv[0] = 0;
		/* Data read. */
		//printf("Server: got message: `%s'", tok);
		if(strncmp(tok, "get main.info", 13) == 0 ||
		   strncmp(tok, "get track.info", 14) == 0) {
			sprintf(recv, 
				"(active_pix %d) "
				"(active_lines %d) "
				"(dummy_pix_start 0) "
				"(dummy_pix_end 0) "
				"(dummy_lines_start 0) "
				"(dummy_lines_end 0) "
				"(pix_x_size 5.4) "
				"(pix_y_size 5.4) "
				"(min_exp 0.001) "
				"(max_exp 3600) "
				"(scale 1.0) "
				"(rdnoise 0.0) "
				"(name 'QHY5')\n", width, height);
		}
		else if(strncmp(tok, "get main.exp", 12) == 0 ||
		        strncmp(tok, "get track.exp", 13) == 0) {
			sprintf(recv,
				"(width %d) "
				"(height %d) "
				"(skip_x 0) "
				"(skip_y 0) "
				"(bin_x 1) "
				"(bin_y 1) "
				"(exptime %f) "
				"(jdate %f)\n", width, height, expose, time_to_jdate(time(NULL)));
		}
		else if(strncmp(tok, "get main.stat", 13) == 0 ||
		        strncmp(tok, "get track.stat", 14) == 0) {
			sprintf(recv,
				"(status %d) "
				"(lines_read 0) "
				"(exp_left 0.0)\n", status);
		}
		else if(strncmp(tok, "get cooler", 10) == 0) {
			strcpy(recv,
				"(temp_set 0.0) "
				"(temp 0.0) "
				"(power 0.0)\n");
		}
		else if(strncmp(tok, "set", 3) == 0) {
			float f;
			sscanf(tok, "set main.exp.exptime %f", &f);
			sscanf(tok, "set track.exp.exptime %f", &f);
			expose = f;
			strcpy(recv, "ok\n");
		}
		else if(strncmp(tok, "exp main", 8) == 0 ||
		        strncmp(tok, "exp track", 9) == 0) {
			strcpy(recv, "ok\n");
			start_read(usbhandle, floor(1000 * expose));
			printf("Exposing for %f secs\n", expose);
			image_ptr = (unsigned char *)mapimage;
			status = 5;
		}
		else if(strncmp(tok, "read main", 8) == 0 ||
		        strncmp(tok, "read track", 9) == 0) {
			int len;
			char str[256];
			char header[256];
			sscanf(tok, "read %s %d", str, &len);
			if (image_ptr == (unsigned char *)mapimage) {
				fetch_image(usbhandle, image, real_height * 1558);
				map_image(mapimage, image, 1280, real_height);
			}
			//printf("Sending %d bytes\n", len);
			sprintf(header, "data %d\n", len);
			send(filedes, header, strlen(header), 0);
			send(filedes, image_ptr, len, 0);
			image_ptr += len;
			status = 1;
		}
		if(recv[0] != 0) {
			//printf("Sending: %s", recv);
			send(filedes, recv, strlen(recv), 0);
		}
	}
	return 0;
}

int main(int argc, char **argv)
{
	init_comms();
	int i, j;
	FILE *fh;
	int timeout = 20000;
	unsigned int gain = 10;

	struct option long_options[] = {
		{"gain", required_argument, NULL, 'g'},
		{0, 0, 0, 0}
	};
	
	while (1) {
		char c;
		c = getopt_long (argc, argv, 
                     "g:",
                     long_options, NULL);
		if(c == EOF)
			break;
		switch(c) {
		case 'g':
			gain = strtol(optarg, NULL, 0);
			break;
		}
	}
	usb_init();
 	if ((usbhandle = locate_device(0x16c0, 0x296d))==0) 
	{
		printf("Could not open the QHY5 device\n");
		usb_close(usbhandle);
		return -1;
	}
	real_height = set_size(usbhandle, 1024, 8, 1);
	image = (unsigned char *)malloc(real_height * 1558);
	mapimage = (unsigned short *)malloc(1280 * 1024 * 2);
	image_ptr = (unsigned char *)mapimage;
	read_image(usbhandle, 100, image, real_height * 1558);
	usleep(100000);
	set_size(usbhandle, 1024, gain, 0);

	while (1) {
		struct timeval timeo;
		timeo.tv_sec = 0;
		timeo.tv_usec = timeout;
 		read_fd_set = active_fd_set;
		if (select (FD_SETSIZE, &read_fd_set, NULL, NULL, &timeo) < 0) {
			perror ("select");
			exit (EXIT_FAILURE);
		}
		for (i = 0; i < FD_SETSIZE; ++i) {
			if (FD_ISSET (i, &read_fd_set)) {
				if (i == base_sock) {
					/* Connection request on original socket. */
					int new, size;
					struct sockaddr_in clientname;

					size = sizeof (clientname);
					new = accept (base_sock, (struct sockaddr *) &clientname, &size);
					if (new < 0) {
						perror ("accept");
						continue;
					}
					printf("Server: connect from host %s, port %hd.\n",
						 inet_ntoa (clientname.sin_addr),
						 ntohs (clientname.sin_port));
					FD_SET (new, &active_fd_set);
				} else {
					/* Data arriving on an already-connected socket. */
					if (read_from_client (i) < 0) {
						close (i);
						FD_CLR (i, &active_fd_set);
					}
				}
			}
		}
	}
	return 0;
}


